<template>
  <div class="add-calendar">
    <BaseModal
      v-model="show"
      width="480"
      :title="`${editText}校历`"
      @on-ok="saveForm"
      @on-cancel="cancel"
      @on-visible-change="onChange"
    >
      <Form :model="addForm" ref="addForm" :rules="formValidate" :label-width="120">
        <FormItem label="编码" prop="code">
          <Input type="text" :maxlength="21" v-model="addForm.code" placeholder="例：201808" />
        </FormItem>
        <FormItem label="名称" prop="name">
          <Input
            type="text"
            :maxlength="30"
            v-model="addForm.name"
            placeholder="例：2018~2019学年 第二学期"
          />
        </FormItem>
        <!-- <FormItem label="所在校区" prop="schoolArea">
          <Select v-model="addForm.schoolArea" placeholder="请选择所在校区">
            <Option
              :value="item.code"
              v-for="(item, index) in wholeList.schoolArea"
              :key="index"
            >{{ item.name }}</Option>
          </Select>
        </FormItem>-->
        <FormItem label="人员" prop="person">
          <Select
            v-model="addForm.person"
            filterable
            remote
            :remote-method="remoteMethod"
            :loading="selectLoading"
            placeholder="请选择人员"
          >
            <Option :value="item.id" v-for="(item, index) in personList" :key="index">{{
              item.name
            }}</Option>
          </Select>
        </FormItem>
        <FormItem label="部门" prop="department">
          <!--  v-model="addForm.department" 通过on-change选择值 -->
          <Select
            transfer
            v-if="renderSelect"
            @on-change="changeDeptId"
            placeholder="请选择部门"
            :class="{ placeDeptId: addForm.department }"
          >
            <Tree :data="departTreeList" :load-data="childTreeList" :render="renderData"></Tree>
          </Select>
        </FormItem>
        <FormItem label="学年" prop="semesterYear">
          <InputNumber
            v-model="addForm.semesterYear"
            placeholder="请输入学年"
            style="width:326px"
          />
        </FormItem>
        <FormItem label="学期" prop="semesterNumber">
          <InputNumber
            v-model="addForm.semesterNumber"
            placeholder="请输入学期"
            style="width:326px"
          />
        </FormItem>
        <FormItem label="开始结束日期" prop="dateStart">
          <DatePicker
            type="daterange"
            transfer
            placeholder="请选择学期开始-结束日期"
            style="width:326px"
            v-model="rangeTerm"
            @on-change="ev => (this.rangeTerm = ev)"
            format="yyyy-MM-dd"
          ></DatePicker>
        </FormItem>
        <FormItem label="教学开始结束日期">
          <DatePicker
            type="daterange"
            transfer
            placeholder="请选择教学开始-教学结束日期"
            v-model="rangeTeach"
            @on-change="ev => (this.rangeTeach = ev)"
            style="width:326px"
            format="yyyy-MM-dd"
          ></DatePicker>
        </FormItem>
        <FormItem label="教师报到日期" prop="dateCheckinStaff">
          <DatePicker
            type="date"
            transfer
            placeholder="请选择教师报到日期"
            v-model="addForm.dateCheckinStaff"
            @on-change="ev => (this.addForm.dateCheckinStaff = ev)"
            style="width:326px"
            format="yyyy-MM-dd"
          ></DatePicker>
        </FormItem>
        <FormItem label="学生报到日期" prop="dateCheckinStudent">
          <DatePicker
            type="date"
            transfer
            placeholder="请选择学生报到日期"
            style="width:326px"
            format="yyyy-MM-dd"
            v-model="addForm.dateCheckinStudent"
            @on-change="ev => (this.addForm.dateCheckinStudent = ev)"
          ></DatePicker>
        </FormItem>
        <FormItem label="额外信息" prop="description">
          <Input
            v-model="addForm.description"
            type="textarea"
            :autosize="{ minRows: 4, maxRows: 6 }"
            placeholder="您可以在此处输入基础信息外的校历内容，比如考试周、体育节、答辩日期等等"
          />
        </FormItem>
      </Form>
    </BaseModal>
  </div>
</template>
<script lang="ts">
import { Component, Vue, Prop } from "vue-property-decorator";
import { Tools } from "@/model/tools.ts";
import BaseModal from "@components/iv-modal/BaseModal.vue";
import { CalendarData, WholeData, ParamData } from "@/store/interface.ts";
import {
  addData,
  updateData,
  getWhole,
  getTreeData,
  queryData,
  queryStaffData
} from "@/api/service.js";
import { BASE64, lodash, cloneForm } from "@utils/tools.ts";
import { Form } from "iview";

@Component({
  components: {
    BaseModal
  }
})
export default class AddCalendar extends Tools {
  public $refs!: {
    addForm: Form;
  };
  // 新增
  private formData: CalendarData = {
    code: "",
    name: "",
    schoolArea: "",
    studentStatus: "",
    person: "", //人员
    department: "", // 部门
    semesterYear: this.getYear(), // 重写方法
    semesterNumber: 1,
    dateStart: "",
    dateEnd: "",
    dateStartTeaching: "",
    dateEndTeaching: "",
    dateCheckinStaff: "",
    dateCheckinStudent: "",
    description: ""
  };
  //select 异步
  private wholeData: WholeData = {
    dictName: "",
    filter: true,
    pageNumber: 1,
    pageSize: 100
  };
  //select开启搜索
  private selectData: ParamData = {
    type: "",
    includeAttributes: [],
    criteria: "",
    pageNumber: 1,
    pageSize: 10
  };
  private wholeList: Array<any> = [];
  private personList: Array<any> = [];
  private departTreeList: Array<any> = [];
  private selectLoading: boolean = false;
  private renderSelect: boolean = true; //编辑时强制展示
  // 编辑
  @Prop()
  private rowData!: any; //若定义为object,则 this.rowData.id不存在
  @Prop()
  private value!: boolean;
  @Prop()
  private edit!: boolean;
  // 第一种写法：通过prop传递值,即父组件<AddCalendar :show="calenderModal"></AddCalendar>
  // private show!: boolean;
  //第二种写法： show即计算属性
  private get show() {
    return this.value;
  }
  private set show(value) {
    // 若第一次点击新增，关闭弹层，第二次点击则没有反应,show计算属性值没有及时派发事件
    this.$emit("input", value);
    //第一次弹窗新增层，关闭弹层，第二次点击弹窗，表单需要置空文本域
    this.formReset();
  }

  // 计算属性
  private get rangeTerm() {
    let { dateStart, dateEnd } = this.formData;
    return [dateStart, dateEnd];
  }
  private set rangeTerm(value) {
    this.formData.dateStart = value[0];
    this.formData.dateEnd = value[1];
  }

  //计算属性
  private get rangeTeach() {
    let { dateStartTeaching, dateEndTeaching } = this.formData;
    return [dateStartTeaching, dateEndTeaching];
  }
  private set rangeTeach(value) {
    this.formData.dateStartTeaching = value[0];
    this.formData.dateEndTeaching = value[1];
  }

  private get editText() {
    return this.edit ? "编辑" : "新增";
  }
  /**
   * 提交数据
   */
  private get addForm() {
    if (this.edit) {
      //避免延迟出现
      // this.renderSelect = false;
      // this.$nextTick(() => {
      //   this.renderSelect = true;
      // });

      //iview表单验证包含类型验证，默认为string类型，如果后台返回number验证将不通过
      // 若直接return this.rowData; 则关闭编辑弹框时，数据变化被置空
      // 若下面直接赋值,则点击编辑弹框，然后在点击新增弹框，新增的开始结束日期依旧有值
      // let data = lodash.cloneDeep(this.rowData);
      // this.formData = data;
      // 不可使用深拷贝的原因,在提交编辑信息是报错: 应用不具备对[schoolCalendar].[weeks]的写权限"
      //return Object.assign(this.formData, lodash.cloneDeep(this.rowData));
      let cloneData = cloneForm(this.formData, this.rowData);
      // console.log(cloneData, "cloneData"); // 输出拷贝的值
      // console.log(this.formData, "formData"); //输出空对象
      // 若Object.assign()时，在编辑时开始结束时间可以被赋值，但是点击新增，则开始结束时间却出现上次赋值
      // 出错的原因是,在formReset()方法中 this.addForm = Object.assign({}，this.addForm, { dateStart: "",dateEnd: "",});
      // Object.assign(target, ...sources) 从一个或多个源对象复制到目标对象
      return Object.assign(this.formData, cloneData);
    } else {
      return this.formData;
    }
  }
  private set addForm(value) {}
  /**
   * 计算属性校验
   */
  private get formValidate() {
    const validCode = (rule, val, callback) => {
      const reg = /^[0-9a-zA-Z\u4e00-\u9fa5]+$/; // 正则表达式 /^ $/ 表示反例
      if (!reg.test(val)) {
        callback("编号不允许出现特殊字符");
      } else {
        callback();
      }
    };
    return {
      code: [
        {
          required: true,
          message: "代码不能为空",
          trigger: "blur"
        },
        {
          max: 20,
          message: "编号不能超过20个字", //文本域控制个数，需要额外校验
          trigger: "blur"
        },
        {
          pattern: /^[0-9a-zA-Z\u4e00-\u9fa5]+$/,
          message: "编号不允许出现特殊字符",
          trigger: "blur"
        }
        //第二种校验方法
        // { validator: validCode } //必须分开写
      ],
      name: [
        {
          required: true,
          message: "名称不能为空",
          trigger: "blur"
        }
      ],
      semesterYear: [
        {
          type: "number", //将字符串转换为数字类型
          required: true,
          message: "学年不能为空",
          trigger: "blur"
        }
      ],
      semesterNumber: [
        {
          type: "number",
          required: true,
          message: "学期不能为空",
          trigger: "blur"
        }
      ],
      rangeTerm: [
        {
          required: true,
          message: "开始结束日期不能为空",
          trigger: "blur"
        }
      ],
      person: [
        {
          type: "number",
          required: true,
          message: "请输入人员",
          trigger: "blur"
        }
      ],
      department: [
        // {
        //   type: "number",
        //   required: true,
        //   message: "请选择部门",
        //   trigger: "blur"
        // }
        {
          required: true,
          message: "请选择部门",
          trigger: "change"
        }
      ],
      dateStart: [
        {
          required: true,
          message: "请选择开始结束日期",
          trigger: "blur" // 将blur改为change
        }
      ]
    };
  }

  private getYear(): any {
    return new Date().getFullYear();
  }

  /**
   * 提交数据,计算属性而不是methods
   */
  private get paramData() {
    const data = {
      type: "schoolCalendar",
      attributes: BASE64.encode(JSON.stringify(this.addForm))
    };
    if (this.edit) {
      return {
        id: this.rowData.id,
        ...data
      };
    } else {
      return data;
    }
  }
  /**
   * select开启远程搜索
   */
  private async remoteMethod(query) {
    let _this = this;
    if (query !== "") {
      _this.selectLoading = true;
      _this.personList = await _this.getSelectPersonData(query);
      _this.selectLoading = false;
    } else {
      _this.personList = [];
    }
  }
  /**
   * 选择部门
   */
  private changeDeptId(row) {
    //this.addForm.department = row;
    this.addForm.department = row.toString();
  }

  private saveForm() {
    this.$refs.addForm.validate(valid => {
      if (valid) {
        this.submitForm(); //提交
      }
    });
  }

  //关闭弹窗层,取消校验
  private cancel() {
    this.$refs.addForm.resetFields();
    this.show = false;
  }

  private async submitForm() {
    // this.show = false; 当此处若将show=false时,则会立即调用formReset()方法,从而导致表单域被置空,应该数据异步成功之后在调用
    const [err, data] = this.edit
      ? await updateData(this.paramData)
      : await addData(this.paramData);
    if (err) {
      let msg = err.message || `${this.editText}校历信息异常`;
      this.$Message.error(msg);
      return;
    }
    this.$Message.success(`${this.editText}校历信息成功`); //保存列表成功,刷新列表数据
    this.show = false; //弹出层消失，且调用formReset()
    this.$emit("on-update"); // 通过$emit派发on-update调用父组件AddCalendar中getList()更新列表数据
  }

  private formReset(): void {
    this.$refs.addForm.resetFields();
    // 重置编辑初始值，否则第二次编辑时候不会触发form值更新，此时的form已经被重置
    this.$emit("update:edit", false);
    // 单独设置开始结束日期
    this.addForm = Object.assign(this.addForm, {
      dateStart: "",
      dateEnd: "",
      dateStartTeaching: "",
      dateEndTeaching: ""
    });
  }

  /**
   * 选择部门
   */
  private async getSelectDeptData(id) {
    let param = { objectName: "department", pid: id };
    const [err, data] = await getTreeData(param);
    if (err) {
      let msg = err.message || "请求部门列表异常";
      this.$Message.error(err);
      return;
    }
    return data.map(item => {
      item = Object.assign(
        {},
        {
          value: item.wid,
          title: item.name,
          hasLeaf: item.hasLeaf
        }
      );
      if (item.hasLeaf) {
        item.loading = false;
        item.children = [];
      }
      return item;
    });
  }

  private async childTreeList(item, callback): Promise<any> {
    //根据触发每行tree的父级id获取child数据
    let data = await this.getSelectDeptData(item.value);
    return callback(data);
  }

  /**
   * 渲染tree每一行变成option下拉
   */
  private renderData(h, { data }) {
    return h(
      "Option",
      {
        style: { display: "inline-block", margin: 0 },
        props: {
          value: data.value
        }
      },
      data.title
    );
  }
  /**
   * 选择人员
   */
  private async getSelectPersonData(value): Promise<any> {
    let param = Object.assign(this.selectData, {
      type: "staff",
      criteria: BASE64.encode(
        JSON.stringify([["OR", [["code", "ilike", value], ["name", "ilike", value]]]])
      ),
      includeAttributes: ["code", "name"]
    });
    const [err, data] = await queryStaffData(param);
    if (err) {
      let msg = err.message || "请求人员列表异常";
      this.$Message.error(err);
      return;
    }
    return data.rows;
  }

  private async getSchoolArea() {
    let requestList = ["schoolArea", "studentStatus"];
    //发送多个请求并根据请求顺序获取和使用数据的场景
    Promise.all([
      requestList.map(objItem => {
        //错误的写法  否则每次请求的参数都是最后一个 this.wholeData.dictName = objItem
        let param = Object.assign({}, this.wholeData, {
          dictName: objItem
        });
        return new Promise((resolve, reject) => {
          // 异步
          getWhole(param).then(res => {
            this.commLoading(false);
            let [err, data] = res;
            if (err) {
              this.$Message.error(err.message);
            }
            this.wholeList[objItem] = data.rows;
          });
        });
      })
    ]).catch(error => {
      console.log("请求异常");
    });
  }

  /**
   *  1.若下拉列表新增or修改数据的话,则打开弹窗重新请求数据,若在mounted中写的话，只请求一次，需要刷新页面才进行变化
   *  修复bug问题----
   *  2.通过show的计算属性，进行处理,这种方法或多次调用，不是明智的选择
   */
  onChange(value) {
    if (!value) {
      //通过弹窗层展示 or 隐藏进行调用不同方法
    } else {
      this.getSchoolArea();
      this.getSelectDeptData("").then(res => {
        this.departTreeList = res;
      });
    }
  }
}
</script>
<style lang="stylus" scoped>
.add-calendar {
}
</style>
